/*
 * Copyright (c) 2023 Loongson(GD) Inc.
 * Author: loongson-gz
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "ls_usage_mtd.h"
#include "mtd_io.h"

// 为 1 可以打开调试打印
#define MTD_IO_DEBUG 0

#ifndef IF_RES_FAILURE_RETURN
#define IF_RES_FAILURE_RETURN(x) if (x) \
									return EXIT_FAILURE;
#endif

#ifndef IF_RES_FAILURE_GOTO
#define IF_RES_FAILURE_GOTO(x, y) if (x) {x = EXIT_FAILURE; goto y;}
#endif

#define IO_MTD_TYPE_WRITE 0
#define IO_MTD_TYPE_READ  1

typedef size_t (*f_io_func)(void *ptr, size_t size, size_t nmemb, FILE *stream);
typedef int (*mtd_io_lib_func)(mtd_info* info, unsigned char* data, unsigned long data_size, unsigned long offset);

static int get_file_size(char *file_path, unsigned long* file_size);
static inline int io_mtd_about_file_param_check(char* mtd_name, char* file_path, const char* func_name);

static int io_mtd_deploy_way(mtd_info* mtd_info, unsigned long data_size, unsigned long offset, int* io_size,
								int* align_front_size, int* align_time, int* align_back_size);
static int io_mtd_file_general_handle(mtd_info* info, char* file_path, int handle_type,
									unsigned long data_size, unsigned long offset, int io_size,
									int align_front_size, int align_time, int align_back_size,
									const char* func_name);

// 检查参数合法性的函数
static inline int io_mtd_check_param(char* mtd_name, unsigned char* data, unsigned long data_size, const char* func_name)
{
	if (!mtd_name)
	{
		fprintf(stderr, "Error: %s: param mtd_name is NULL\n", func_name);
		return EXIT_FAILURE;
	}
	if (!data)
	{
		fprintf(stderr, "Error: %s: param data is NULL\n", func_name);
		return EXIT_FAILURE;
	}
	if (!data_size)
	{
		fprintf(stderr, "Error: %s: param data_size is 0\n", func_name);
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

int read_mtd(char* mtd_name, unsigned char* data, unsigned long data_size, unsigned long offset)
{
	int res;
	mtd_info* mtd_info;

	res = io_mtd_check_param(mtd_name, data, data_size, __func__);
	IF_RES_FAILURE_RETURN(res);

	// 申请 mtd_info 结构
	mtd_info = NULL;
	res = open_mtd(mtd_name, &mtd_info);
	IF_RES_FAILURE_RETURN(res);

#if MTD_IO_DEBUG
	printf("Debug: mtd name %s %s\n", mtd_info->mtd_dev_name, mtd_info->mtdblock_dev_name);
	printf("Debug: mtd size %ld eraser size %ld write bsize %d\n", mtd_info->size, mtd_info->erase_size, mtd_info->write_size);
#endif

	// 直接读就好
	res = read_mtdblock(mtd_info, data, data_size, offset);

#if MTD_IO_DEBUG
	printf("Debug:\n");
	for (int i = 0; i < data_size; ++i)
	{
		if (i && i % 16 == 0)
			printf("\n");
		printf("%.2x ", (unsigned char)data[i]);
	}
	printf("\n");
#endif

	close_mtd(&mtd_info);
	return res;
}

int write_mtd(char* mtd_name, unsigned char* data, unsigned long data_size, unsigned long offset)
{
	int res;
	mtd_info* mtd_info;

	res = io_mtd_check_param(mtd_name, data, data_size, __func__);
	IF_RES_FAILURE_RETURN(res);

	// 申请 mtd_info 结构
	mtd_info = NULL;
	res = open_mtd(mtd_name, &mtd_info);
	IF_RES_FAILURE_RETURN(res);

#if MTD_IO_DEBUG
	printf("Debug: mtd name %s %s\n", mtd_info->mtd_dev_name, mtd_info->mtdblock_dev_name);
	printf("Debug: mtd size %ld eraser size %ld write bsize %d\n", mtd_info->size, mtd_info->erase_size, mtd_info->write_size);
#endif

	// 直接写就好
	res = write_mtdblock(mtd_info, data, data_size, offset);

	close_mtd(&mtd_info);
	return res;
}

/*
 * 下面是针对数据流是文件的两个操作函数
 * 是需要多次调用 write_mtdblock 或者 read_mtdblock的。
 * 因为不知道文件有多大。
 *
 * io_mtd_deploy_way 函数是为了得出如何读写比较好。
 * 因为即使mtdblock的写不用考虑如何 erase。
 * 但是 offset 和 erase_size 不对齐的话，那么就会发生同一块区域多次清除后写入。
 * 比如 erase_size 为 4096，data_size 为 4097，offset为4095。
 * 那么 按照刷写块来说，应该是 1 + 4096 + 1 这样写比较好
 * 但如果是 4096 + 2 这样写，写 4096 字节的时候会有两个块刷写了
 * 然后写 2 字节的时候，也还是会有两个块刷写了。
 * 所以 io_mtd_deploy_way 函数就是算出如何写的策略的函数。
 *
 * io_mtd_file_general_handle 函数则是进行 读写的函数。
 * 有了前面的计算就知道如何写了。
 */

int read_mtd_to_file(char* mtd_name, char* file_path, unsigned long data_size, unsigned long offset)
{
	int res;
	int io_size; //通常是 erase_size
	int io_mtd_front_size;
	int io_mtd_time;
	int io_mtd_back_size;
	mtd_info* mtd_info;

	// 参数检查
	res = io_mtd_about_file_param_check(mtd_name, file_path, __func__);
	IF_RES_FAILURE_RETURN(res);

	// 获取一个mtd_info
	mtd_info = NULL;
	res = open_mtd(mtd_name, &mtd_info);
	IF_RES_FAILURE_RETURN(res);

	// 检查从偏移处 + 获取的大小 会不会超过mtd区域大小 因为是分多次操作 所以需要自行判断能否够空间写入
	if (mtd_info->size < (offset + data_size))
	{
		fprintf(stderr, "Error: %s: %s file too big(mtd size: %ld, file size: %ld, offset: %ld)!\n", __func__, file_path,
				mtd_info->size , data_size, offset);
		IF_RES_FAILURE_GOTO(res, read_mtd_to_file_close_mtd);
	}

	// data_size == 0 代表读取 offset 之后的所有数据
	data_size = data_size ? data_size : (mtd_info->size - offset);
	// 读的一次大小其实没有什么所谓 不是写，不需要 erase
	res = io_mtd_deploy_way(mtd_info, data_size, offset, &io_size, &io_mtd_front_size, &io_mtd_time, &io_mtd_back_size);
	IF_RES_FAILURE_GOTO(res, read_mtd_to_file_close_mtd);

	res = io_mtd_file_general_handle(mtd_info, file_path, IO_MTD_TYPE_READ,
								data_size, offset, io_size,
								io_mtd_front_size, io_mtd_time, io_mtd_back_size,
								__func__);

read_mtd_to_file_close_mtd:
	close_mtd(&mtd_info);
	return res;
}

int write_mtd_by_file(char* mtd_name, char* file_path, unsigned long data_size, unsigned long offset)
{
	int res;
	int io_size;
	int io_mtd_front_size;
	int io_mtd_time;
	int io_mtd_back_size;
	unsigned long file_size = 0;
	mtd_info* mtd_info;

	// 参数检查
	res = io_mtd_about_file_param_check(mtd_name, file_path, __func__);
	IF_RES_FAILURE_RETURN(res);

	// 得到文件大小
	res = get_file_size(file_path, &file_size);
	if (res)
	{
		fprintf(stderr, "Error: %s: %s file cant open!\n", __func__, file_path);
		return EXIT_FAILURE;
	}

	//  获取一个mtd_info
	mtd_info = NULL;
	res = open_mtd(mtd_name, &mtd_info);
	IF_RES_FAILURE_RETURN(res);

	// 如果过指定的写入大小比文件还大，那么参数错误
	if (file_size < data_size)
	{
		fprintf(stderr, "Error: %s: %s file size is %ld but param data_size is : %ld!\n", __func__, file_path,
				file_size, data_size);
		IF_RES_FAILURE_GOTO(res, write_mtd_by_file_close_mtd);
	}

	// 参数是0 代表全文件写入，否则按照给的大小写入
	data_size = data_size ? data_size : file_size;

	// 检查从偏移处 + 要写入的大小 会不会超过mtd区域大小 因为是分多次操作 所以需要自行判断能否够空间写入
	if (mtd_info->size < (offset + data_size))
	{
		fprintf(stderr, "Error: %s: %s file too big(mtd size: %ld, file size: %ld, offset: %ld)!\n", __func__, file_path,
				mtd_info->size , file_size, offset);
		IF_RES_FAILURE_GOTO(res, write_mtd_by_file_close_mtd);
	}

	res = io_mtd_deploy_way(mtd_info, data_size, offset, &io_size, &io_mtd_front_size, &io_mtd_time, &io_mtd_back_size);
	IF_RES_FAILURE_GOTO(res, write_mtd_by_file_close_mtd);

	res = io_mtd_file_general_handle(mtd_info, file_path, IO_MTD_TYPE_WRITE,
								data_size, offset, io_size,
								io_mtd_front_size, io_mtd_time, io_mtd_back_size,
								__func__);

write_mtd_by_file_close_mtd:
	close_mtd(&mtd_info);
	return res;
}

static int io_mtd_deploy_way(mtd_info* mtd_info, unsigned long data_size, unsigned long offset, int* io_size,
								int* align_front_size, int* align_time, int* align_back_size)
{
	unsigned long data_size_bak = data_size;
	if (!mtd_info || !data_size || !io_size || !align_front_size || !align_time || !align_back_size)
	{
		return EXIT_FAILURE;
	}

	if (mtd_info->size < (offset + data_size))
	{
		return EXIT_FAILURE;
	}

	io_size[0] = mtd_info->erase_size;

	// 如果 offset 是0，代表本来就对齐。
	// (offset % io_size[0]) 得到的是距离上一个对齐边界的长度，但是要的是距离下一个对齐边界的长度，所以要
	// io_size[0] - (offset % io_size[0])
	// 比如 offset=4095 erase_size=4096，4095 % 4096=4095 但是明显是需要先写一个字节，这样才能对应4096。
	align_front_size[0] = offset ? io_size[0] - (offset % io_size[0]) : 0;
	// 如果写的数据太少了，那么只需要写这个所谓的前对齐数据即可
	// 比如 offset=4 data_size=16 erase_size=4096，那么前一条算出来是4092，其实是无需写这么多的。
	align_front_size[0] = (align_front_size[0] >= data_size) ? data_size : align_front_size[0];
	// data_size = (data_size <= align_front_size[0]) ? 0 : (data_size - align_front_size[0]);
	// 除去前面的对齐，剩下要写的
	data_size = data_size - align_front_size[0];
	align_back_size[0] = data_size % io_size[0]; //  直接取余即可
	data_size -= align_back_size[0]; // 接下来算中间对齐的大小
	align_time[0] = data_size / io_size[0]; // 中间的数据要写多少次

	// 策略制定后，如果读写大小和原本希望的不一样，代表上面的代码出问题了，这是算法出错了
	data_size = align_front_size[0] + (align_time[0] * io_size[0]) + align_back_size[0];
	if (data_size_bak != data_size)
	{
		printf("Error: %s: data_size: %ld, offset: %ld, iosize: %d, front: %d, time: %d, back: %d\n", __func__,
				data_size_bak, offset,
				io_size[0], align_front_size[0], align_time[0], align_back_size[0]);
	}
#if MTD_IO_DEBUG
	printf("Debug: %s: data_size: %ld, offset: %ld, iosize: %d, front: %d, time: %d, back: %d\n", __func__,
			data_size_bak, offset,
			io_size[0], align_front_size[0], align_time[0], align_back_size[0]);
#endif
	return EXIT_SUCCESS;
}

// io_mtd_file_general_handle_transfer 用于 io_mtd_file_general_handle
static int io_mtd_file_general_handle_transfer(mtd_info* info, FILE* fp, int handle_type, unsigned char* binary_data,
												int t_size, int* offset, unsigned long * total_data)
{
	int fcount;
	// 写mtd代表要读文件 读mtd代表要写文件
	f_io_func f_io_func_set[] = {fread, (f_io_func)fwrite};
	mtd_io_lib_func mtd_io_lib_func_set[] = {write_mtdblock, read_mtdblock};

	if (handle_type == IO_MTD_TYPE_READ)
	{
		mtd_io_lib_func_set[handle_type](info, binary_data, t_size, offset[0]);
		fcount = f_io_func_set[handle_type](binary_data, t_size, 1, fp);
	}
	else if (handle_type == IO_MTD_TYPE_WRITE)
	{
		fcount = f_io_func_set[handle_type](binary_data, t_size, 1, fp);
		mtd_io_lib_func_set[handle_type](info, binary_data, t_size, offset[0]);
	}
	else
	{
		return EXIT_FAILURE;
	}

	offset[0] += t_size;
	total_data[0] += fcount ? t_size : 0;
	return EXIT_SUCCESS;
}

static int io_mtd_file_general_handle(mtd_info* info, char* file_path, int handle_type,
									unsigned long data_size, unsigned long offset, int io_size,
									int align_front_size, int align_time, int align_back_size,
									const char* func_name)
{

	FILE* fp = NULL;
	unsigned char* binary_data = NULL;
	int cur_offset;
	unsigned long total_data;
	// 写mtd代表要读文件 读mtd代表要写文件
	const char* fopen_flags[] = {"rb", "wb"};

	if (!info || !file_path || !data_size || !io_size)
	{
		return EXIT_FAILURE;
	}
	if (info->size < (offset + data_size))
	{
		return EXIT_FAILURE;
	}
	if (data_size != (align_front_size + align_back_size + (io_size * align_time)))
	{
		return EXIT_FAILURE;
	}
	handle_type = handle_type ? IO_MTD_TYPE_READ : IO_MTD_TYPE_WRITE;

	fp = fopen(file_path, fopen_flags[handle_type]);  //二进制打开，写文件
	if(!fp)
	{
		fprintf(stderr, "Error: %s->%s: %s file cant open!\n", func_name, __func__, file_path);
		return EXIT_FAILURE;
	}

	cur_offset = offset;
	binary_data = (unsigned char*)calloc(io_size, sizeof(unsigned char));
	total_data = 0;

	// 前 对齐 IO
	if (align_front_size)
	{
		io_mtd_file_general_handle_transfer(info, fp, handle_type, binary_data, align_front_size, &cur_offset, &total_data);
	}

	// 中间 都是 对齐 io_size
	if (align_time)
	{
		while(align_time)
		{
			io_mtd_file_general_handle_transfer(info, fp, handle_type, binary_data, io_size, &cur_offset, &total_data);
			--align_time;
		}
	}

	// 后 对齐 IO
	if (align_back_size)
	{
		io_mtd_file_general_handle_transfer(info, fp, handle_type, binary_data, align_back_size, &cur_offset, &total_data);
	}
	fclose(fp);
	free(binary_data);

	if (total_data != data_size)
	{
		fprintf(stderr, "Error: %s->%s: %s file failed!", func_name, __func__, file_path);
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

static int get_file_size(char *file_path, unsigned long* file_size)
{
	FILE *fp = NULL;

	if (!file_path || !file_size)
	{
		return EXIT_FAILURE;
	}

	fp = fopen(file_path , "r");
	if(!fp)
	{
		return EXIT_FAILURE;
	}

	// 定位到最后
	fseek(fp, 0L, SEEK_END);
	// 知道距离开始多少字节，也就知道文件大小了
	file_size[0] = ftell(fp);

	fclose(fp);

	return EXIT_SUCCESS;
}

static inline int io_mtd_about_file_param_check(char* mtd_name, char* file_path, const char* func_name)
{
	if (!mtd_name)
	{
		fprintf(stderr, "Error: %s: param mtd_name is NULL\n", func_name);
		return EXIT_FAILURE;
	}
	if (!file_path)
	{
		fprintf(stderr, "Error: %s: param file_path is NULL\n", func_name);
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}